package cn.flying.base.web.auth.configuration;

import javax.annotation.Resource;

import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.session.HttpSessionEventPublisher;

import cn.flying.base.web.auth.handler.CustomAccessDeniedHandler;
import cn.flying.base.web.auth.handler.CustomLogoutSuccessHandler;
import cn.flying.base.web.auth.handler.LoginFailureHandler;
import cn.flying.base.web.auth.handler.LoginSuccessHandler;
import cn.flying.base.web.auth.handler.UnauthorizedEntryPointHandler;
import cn.flying.base.web.auth.session.SessionExpiredStrategy;
import cn.flying.base.web.auth.session.SessionInvalidStrategy;

/**
 * @description: security 配置类
 * @author: lvyong
 * @date: 2022年11月07日 20:36
 * @version: 1.0
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
//@EnableGlobalAuthentication
@EnableWebSecurity(debug = false)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Resource
    private LoginUserDetailService loginUserDetailService;
    @Resource
    private LoginSuccessHandler loginSuccessHandler;
    @Resource
    private LoginFailureHandler loginFailureHandler;
    @Resource
    private UnauthorizedEntryPointHandler unauthorizedEntryPointHandler;
    @Resource
    private CustomAccessDeniedHandler customAccessDeniedHandler;
    @Resource
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;
    @Resource
    private SessionExpiredStrategy sessionExpiredStrategy;
    @Resource
    private SessionInvalidStrategy sessionInvalidStrategy;
    @Resource
    private CustomWebAuthenticationDetailsSource customWebAuthenticationDetailsSource;

    /**
     * 登录前进行过滤
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启httpBasic验证
        http.httpBasic()
                //指定authenticationDetailsSource
                .authenticationDetailsSource(customWebAuthenticationDetailsSource)
                .and()
                //登出配置
                .logout()
                .logoutUrl("/doLogout")
                .logoutSuccessHandler(customLogoutSuccessHandler)
                //使用用户的session失效
                .invalidateHttpSession(true)
                .and()
                //禁用匿名登录
                .anonymous().disable()
                //开启记住密码
//                .rememberMe()
//                .rememberMeParameter("rememberMe")
//                .rememberMeCookieName("remember-me-cookie")
//                .tokenValiditySeconds(2 * 24 * 60 * 60)
//                .tokenRepository()
//                .and()
                //设置需要拦截的请求
                .authorizeRequests()
                //放行请求，这里的所有请求必须是post提交
                .antMatchers("/doLogin", "/doRegister").permitAll()
                //其他的请求都需要进行身份认证
                .anyRequest().authenticated()
                //对于所有的请求都需要采用该方法校验是否有权限
//                .anyRequest().access("@rbacService.hasPermission(request, authentication)")
                .and()
                .exceptionHandling()
                //匿名无权限访问
                .authenticationEntryPoint(unauthorizedEntryPointHandler)
                //认证用户无权限访问
                .accessDeniedHandler(customAccessDeniedHandler)
                .and()
                //session管理配置
                .sessionManagement()
                //是session的生成策略，前后端分离的时候配置成STATELESS，非前后端分离的需要配置成ALWAYS，这里用ALWAYS，即总是创建HttpSession，默认是IF_REQUIRED
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //session失效后跳转路径（会话过期）
//                .invalidSessionUrl("/login")
                //session失效策略，和上面invalidSessionUrl二选一
                .invalidSessionStrategy(sessionInvalidStrategy)
                //session保护  migrateSession默认（防御会话固定攻击）
                .sessionFixation().migrateSession()
                //限制最大登录用户（客户端）数 （会话并发控制）
                .maximumSessions(1)
                //true 表示已经登录了就不能在登录了，false表示允许再次登录但是会将原来登录的用户踢下线，使用这个需要配置一个Bean（HttpSessionEventPublisher）
                .maxSessionsPreventsLogin(false)
//                .expiredUrl("")
                //session到期策略
                .expiredSessionStrategy(sessionExpiredStrategy)
                .and()
                //开启跨域配置
                .and()
                .cors()
                // 禁用csrf防御机制,是禁用使用跨站请求,可解决POST请求403问题
                .and()
                .csrf().disable().headers().frameOptions().disable();
    }

    /**
     * 配置忽略资源（这里只添加了验证码获取）
     * 一般是配置系统静态资源用，配置的请求根本不会进入 spring security 的过滤器链，直接放行
     * .antMatchers("/").permitAll() 是会进入 spring security 的过滤器链的，这是 2 者的主要区别
     * /login（登录）、/captcha（获取图片验证码）、/forget（忘记密码）、/register（注册）、/error（错误页面）
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/captcha",
                "/druid/**"

        );
    }

    /**
     * 配置认证处理器
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(loginUserDetailService).passwordEncoder(passwordEncoder());
    }

    /**
     * 加密处理对象
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 初始化一个自定义的校验provider
     *
     * @return
     */
    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        CustomAuthenticationProvider provider = new CustomAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsService(loginUserDetailService);
        return provider;
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(customAuthenticationProvider());
    }

    /**
     * logout 退出后需要发布一个事件，通过将HttpSessionEventPublisher注入到ioc容器中，实现session的清除
     *
     * @return
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}
